package org.simpleframework.aop;

import lombok.Getter;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;

import org.simpleframework.aop.aspect.AspectInfo;
import org.simpleframework.util.ValidationUtil;

/**
 * @author icanci
 * @date Created in 2021/03/03 22:03
 * @version 1.0
 */
public class AspectListExecutor implements MethodInterceptor {
    /**
     * 被代理的类
     */
    private Class<?>         targetClass;
    /**
     *  aspectList
     */
    @Getter
    private List<AspectInfo> sortAspectInfoList;

    public AspectListExecutor(Class<?> targetClass, List<AspectInfo> aspectList) {
        this.targetClass = targetClass;
        this.sortAspectInfoList = sortAspectInfoList(aspectList);
    }

    /**
     * 排序
     * @param aspectList 需要排序的集合
     * @return 返回升序排序结果
     */
    private List<AspectInfo> sortAspectInfoList(List<AspectInfo> aspectList) {
        aspectList.sort((o1, o2) -> {
            return o1.getOrderIndex() - o2.getOrderIndex();
        });
        return aspectList;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Object returnObject = null;
        collectAccurateMatchedAspectList(method);
        System.out.println(sortAspectInfoList.size());
        if (ValidationUtil.isEmpty(sortAspectInfoList)) {
            System.out.println("proxy111:" + proxy);
            System.out.println("args:" + args);
            returnObject = proxy.invokeSuper(obj, args);
            return returnObject;
        }
        try {
            // 1.按照Order的顺序升序执行完所有的Aspect的before方法
            invokeBeforeAdvices(method, args);
            // 2.执行被代理类的方法
            returnObject = proxy.invokeSuper(obj, args);
            // 3.如果被代理的方法正常返回，则按照order的顺序降序执行完所有的Aspect的afterReturning方法
            invokeAfterReturningAdvices(method, args, returnObject);
        } catch (Exception e) {
            // 4.如果被代理的方法抛出异常，则按照order的顺序降序执行完所有的Aspect的afterThrowing方法
            invokeAfterThrowingAdvices(method, args, e);
        }
        return returnObject;
    }

    /**
     * 精确筛选的方法
     * @param method 方法
     */
    private void collectAccurateMatchedAspectList(Method method) {
        System.out.println("size:" + sortAspectInfoList.size());
        System.out.println("me name:" + method.getName());
        if (ValidationUtil.isEmpty(sortAspectInfoList)) {
            return;
        }
        Iterator<AspectInfo> iterator = sortAspectInfoList.iterator();
        while (iterator.hasNext()) {
            AspectInfo aspectInfo = iterator.next();
            if (!aspectInfo.getPointcutLocator().accurateMatches(method)) {
                iterator.remove();
            }
        }
    }

    /**
     * 按照Order的顺序升序执行完所有的Aspect的before方法
     * @param method 方法
     * @param args 参数
     */
    private void invokeBeforeAdvices(Method method, Object[] args) throws Throwable {
        for (AspectInfo aspectInfo : sortAspectInfoList) {
            aspectInfo.getAspectObject().before(targetClass, method, args);
        }
    }

    /**
     * 如果被代理的方法正常返回，则按照order的顺序降序执行完所有的Aspect的afterReturning方法
     * @param method 方法
     * @param args 参数
     * @param returnObject 返回值
     * @return 返回值
     */
    private Object invokeAfterReturningAdvices(Method method, Object[] args, Object returnObject) throws Throwable {
        Object result = null;
        for (int i = sortAspectInfoList.size() - 1; i >= 0; i--) {
            result = sortAspectInfoList.get(i).getAspectObject().afterReturning(targetClass, method, args, returnObject);
        }
        return result;
    }

    /**
     * 如果被代理的方法抛出异常，则按照order的顺序降序执行完所有的Aspect的afterThrowing方法
     * @param method 方法
     * @param args 参数
     * @param e 异常
     */
    private void invokeAfterThrowingAdvices(Method method, Object[] args, Exception e) throws Throwable {
        for (int i = sortAspectInfoList.size() - 1; i >= 0; i--) {
            sortAspectInfoList.get(i).getAspectObject().afterThrowing(targetClass, method, args, e);
        }
    }

}
